package org.goj.practice.service.impl;

import cn.hutool.core.lang.Assert;
import cn.hutool.core.util.ObjectUtil;
import cn.hutool.core.util.StrUtil;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import org.goj.common.constant.ResultCode;
import org.goj.common.entity.practice.Problem;
import org.goj.common.model.RequestUtil;
import org.goj.common.model.Result;
import org.goj.common.model.UbuntuEntity;
import org.goj.common.utils.ExecuteShellUtil;
import org.goj.feign.feign.BaseFeignClient;
import org.goj.practice.dto.ProblemDTO;
import org.goj.practice.dto.SimpleContestProblemDTO;
import org.goj.practice.mapper.ClassifyMapper;
import org.goj.practice.mapper.ProblemMapper;
import org.goj.practice.service.ProblemService;
import org.goj.practice.utils.ProblemList;
import org.goj.practice.utils.XStreamUtil;
import org.goj.practice.vo.*;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.io.ByteArrayResource;
import org.springframework.mock.web.MockMultipartFile;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.web.multipart.MultipartFile;
import org.w3c.dom.Document;

import javax.annotation.Resource;
import javax.servlet.ServletOutputStream;
import javax.servlet.http.HttpServletResponse;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.transform.Transformer;
import javax.xml.transform.TransformerFactory;
import javax.xml.transform.dom.DOMSource;
import javax.xml.transform.stream.StreamResult;
import java.io.*;
import java.nio.charset.StandardCharsets;
import java.util.*;
import java.util.stream.Collectors;
import java.util.stream.IntStream;

/**
 * <p>
 * 题目 服务实现类
 * </p>
 *
 * @author CK
 * @since 2020-12-02
 */
@Service
public class ProblemServiceImpl extends ServiceImpl<ProblemMapper, Problem> implements ProblemService {

    @Resource
    private ProblemMapper problemMapper;

    @Autowired
    private UbuntuEntity ubuntuEntity;

    @Resource
    private ClassifyMapper classifyMapper;

    /**
     * 执行CMD命令，执行次数多的话由于每次初始化SSH连接导致运行速度缓慢
     *
     * @param cmd 命令
     * @return String
     * @author CK
     * @date 2021/5/12 11:41
     */
    private String execCmd(String cmd) {
        ExecuteShellUtil executeShellUtil = ExecuteShellUtil.getInstance();
        String result;
        try {
            executeShellUtil.init(ubuntuEntity);
            result = executeShellUtil.execCmd(cmd);
        } catch (Exception e) {
            e.printStackTrace();
            result = "error";
        }
        return result;
    }

    @Override
    public Page<ProblemVO> getProblemConditionPage(Integer current, Integer size, List<Integer> classifyIdList, Integer sort, String name) {
        //通过奇偶数判断各个条件的排序方式
        Map<Integer, String> sortMap = Map.of(1, "problem_id", 2, "problem_id", 3, "accepted", 4, "accepted",
                5, "submit", 6, "submit", 7, "ratio", 8, "ratio");
        Page<ProblemVO> page = new Page<>(current, size);
        QueryWrapper<ProblemVO> queryWrapper = new QueryWrapper<>();
        queryWrapper.in(ObjectUtil.isNotEmpty(classifyIdList), "classify.classify_id", classifyIdList)
                .and(StrUtil.isNotBlank(name), t -> t.like("problem_id", name).or().like("title", name).or().like("description", name))
                .eq("problem.defunct", "N")
                .orderBy(sort != 0, (sort & 1) == 1, sortMap.get(sort));
        return problemMapper.getProblemConditionPage(page, queryWrapper);
    }

    @Override
    public Page<ProblemAdminVO> getProblemAdminPage(Integer current, Integer size, List<Integer> classifyIdList, Integer sort, String name) {
        Map<Integer, String> sortMap = Map.of(1, "problem_id", 2, "problem_id", 3, "solved", 4, "solved", 5, "in_date", 6, "in_date");
        Page<ProblemAdminVO> page = new Page<>(current, size);
        QueryWrapper<ProblemAdminVO> queryWrapper = new QueryWrapper<>();
        queryWrapper.in(ObjectUtil.isNotEmpty(classifyIdList), "classify.classify_id", classifyIdList)
                .and(StrUtil.isNotBlank(name), t -> t.like("problem_id", name).or().like("title", name).or().like("description", name))
                .orderByAsc("defunct")
                .orderBy(sort != 0, (sort & 1) == 1, sortMap.get(sort));
        return problemMapper.getProblemAdminPage(page, queryWrapper);
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public boolean insertProblem(Problem problem) {
        problem.setDefunct("false".equals(problem.getDefunct()) ? "N" : "Y").setInDate(new Date());
        int num = problemMapper.insertProblem(problem);
        Assert.isTrue(num == 1, "添加题目失败！");

        //远程执行Shell指令创建测试数据
        String dir = "/home/judge/data/" + problem.getProblemId() + "/";
        String result = execCmd("mkdir " + dir + "\n" +
                "touch " + dir + "sample.in" + "\n" +
                "touch " + dir + "sample.out" + "\n" +
                "touch " + dir + "test0.in" + "\n" +
                "touch " + dir + "test0.out" + "\n" +
                "echo '" + problem.getSampleInput() + "' > " + dir + "sample.in" + "\n" +
                "echo '" + problem.getSampleOutput() + "' > " + dir + "sample.out" + "\n" +
                "echo '" + problem.getTestInput() + "' > " + dir + "test0.in" + "\n" +
                "echo '" + problem.getTestOutput() + "' > " + dir + "test0.out");
        Assert.isTrue(!"error".equals(result), "创建题目数据失败！");
        return true;
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public boolean updateProblem(Problem problem) {
        problem.setDefunct("false".equals(problem.getDefunct()) ? "N" : "Y");
        int num = problemMapper.updateById(problem);
        Assert.isTrue(num == 1, "修改题目失败！");

        String dir = "/home/judge/data/" + problem.getProblemId() + "/";
        String result = execCmd(
                "echo '" + problem.getSampleInput() + "' > " + dir + "sample.in" + "\n" +
                        "echo '" + problem.getSampleOutput() + "' > " + dir + "sample.out" + "\n" +
                        "echo '" + problem.getTestInput() + "' > " + dir + "test0.in" + "\n" +
                        "echo '" + problem.getTestOutput() + "' > " + dir + "test0.out");
        Assert.isTrue(!"error".equals(result), "修改题目数据失败！");
        return true;
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public boolean deleteProblem(Integer problemId) {
        problemMapper.deleteById(problemId);
        String result = execCmd("rm -rf /home/judge/data/" + problemId);
        Assert.isTrue(!"error".equals(result), "删除题目失败！");
        return true;
    }

    @Override
    public Problem getProblemAdminById(Integer problemId) {
        Problem problem = problemMapper.selectById(problemId);
        if (problem == null) {
            return null;
        }
        //获取分类ID及其父ID
        List<Integer> classifyIds = Arrays.stream(classifyMapper.getAllByClassifyId(problem.getClassifyId())
                .split("/")).map(Integer::parseInt).collect(Collectors.toList());
        problem.setClassifyIds(classifyIds);
        String testInput = execCmd("cat /home/judge/data/" + problemId + "/test0.in");
        String testOutput = execCmd("cat /home/judge/data/" + problemId + "/test0.out");
        Assert.isTrue(!"error".equals(testInput) && !"error".equals(testOutput), "获取题目信息失败！");
        return problem.setTestInput(testInput.substring(1)).setTestOutput(testOutput.substring(1));
    }

    @Override
    public List<TestDataVO> getTestData(Integer problemId) {
        String result = execCmd("ls -l --time-style '+%Y/%m/%d %H:%M:%S' /home/judge/data/" + problemId + " | grep ^-");
        Assert.isTrue(!"error".equals(result), "读取测试数据出错");
        //将ls读取的结果转换成二维数组
        String[][] results = Arrays.stream(result.replaceFirst("\n", "")
                .replaceAll("[ ]+", " ").replaceAll("\"", "").split("\n"))
                .map(t -> t.split(" ")).toArray(String[][]::new);
        List<TestDataVO> list = new ArrayList<>();
        for (String[] tmp : results) {
            TestDataVO testDataVO = new TestDataVO();
            testDataVO.setName(tmp[7]).setPermission(tmp[0]).setOwner(tmp[2]).setGroup(tmp[3])
                    .setSize(Integer.valueOf(tmp[4])).setDate(tmp[5] + " " + tmp[6]);
            list.add(testDataVO);
        }
        return list;
    }

    @Override
    public boolean deleteTestData(String name, Integer problemId) {
        String result = execCmd("rm -rf /home/judge/data/" + problemId + "/" + name + "*");
        Assert.isTrue(!"error".equals(result), "删除题目测试数据失败！");
        return true;
    }

    @Override
    public boolean addTestData(Integer problemId, String name, String inputTestData, String outputTestData) {
        String dir = "/home/judge/data/" + problemId + "/";
        name = "test" + (Integer.parseInt(name.substring(4)) + 1);
        String result = execCmd(
                "touch " + dir + name + ".in" + "\n" +
                        "touch " + dir + name + ".out" + "\n" +
                        "echo '" + inputTestData + "' > " + dir + name + ".in" + "\n" +
                        "echo '" + outputTestData + "' > " + dir + name + ".out");
        Assert.isTrue(!"error".equals(result), "创建题目测试数据失败！");
        return true;
    }

    @Override
    public List<String> getTestDataByName(String name, Integer problemId) {
        String inputTestData = execCmd("cat /home/judge/data/" + problemId + "/" + name + ".in");
        String outputTestData = execCmd("cat /home/judge/data/" + problemId + "/" + name + ".out");
        Assert.isTrue(!"error".equals(inputTestData) && !"error".equals(outputTestData), "获取题目测试数据失败！");
        List<String> list = new ArrayList<>();
        list.add(inputTestData.substring(1));
        list.add(outputTestData.substring(1));
        return list;
    }

    @Override
    public boolean updateTestDataByName(String name, Integer problemId, String inputTestData, String outputTestData) {
        String dir = "/home/judge/data/" + problemId + "/";
        String result = execCmd(
                "echo '" + inputTestData + "' > " + dir + name + ".in" + "\n" +
                        "echo '" + outputTestData + "' > " + dir + name + ".out");
        Assert.isTrue(!"error".equals(result), "更新题目测试数据失败！");
        return true;
    }

    @Override
    public List<UploadProblemVO> uploadProblem(MultipartFile file) {
        try {
            DocumentBuilderFactory dbFactory = DocumentBuilderFactory.newInstance();
            DocumentBuilder dBuilder = dbFactory.newDocumentBuilder();
            Document doc = dBuilder.parse(file.getInputStream());
            DOMSource domSource = new DOMSource(doc);
            StringWriter writer = new StringWriter();
            StreamResult result = new StreamResult(writer);
            TransformerFactory tf = TransformerFactory.newInstance();
            Transformer transformer = tf.newTransformer();
            transformer.transform(domSource, result);
            ProblemList problemList = (ProblemList) XStreamUtil.toBean(ProblemList.class, writer.toString());
            List<Problem> list = problemList.getProblemList().stream().map(t -> {
                Problem problem = new Problem();
                BeanUtils.copyProperties(t, problem);
                //默认未分类
                problem.setClassifyId(9001001).setInDate(new Date()).setDefunct("N");
                return problem;
            }).collect(Collectors.toList());
            return realUploadProblem(list);
        } catch (Exception e) {
            e.printStackTrace();
            return List.of();
        }
    }

    @Transactional(rollbackFor = Exception.class)
    public List<UploadProblemVO> realUploadProblem(List<Problem> list) {
        int a = problemMapper.insertProblemBatch(list);
        Assert.isTrue(a > 0, "导入题目失败！");
        return list.stream().map(t -> {
            UploadProblemVO uploadProblemVO = new UploadProblemVO();
            uploadProblemVO.setTitle(t.getTitle());
            String dir = "/home/judge/data/" + t.getProblemId() + "/";
            boolean flag = true;
            StringBuilder execStr = new StringBuilder(
                    "mkdir " + dir + "\n" +
                            "touch " + dir + "sample.in" + "\n" +
                            "touch " + dir + "sample.out" + "\n" +
                            "touch " + dir + "test0.in" + "\n" +
                            "touch " + dir + "test0.out" + "\n");
            try {
                if (StrUtil.isNotEmpty(t.getSampleInput()) && t.getTestInputs().size() > 0) {
                    execStr.append("echo '" + t.getTestInputs().get(0) + "' > " + dir + "test0.in" + "\n");
                    execStr.append("echo '" + t.getSampleInput() + "' > " + dir + "sample.in" + "\n");
                } else if (StrUtil.isNotEmpty(t.getSampleInput()) && t.getTestInputs().size() < 1) {
                    execStr.append("echo '" + t.getSampleInput() + "' > " + dir + "sample.in" + "\n");
                    execStr.append("echo '" + t.getSampleInput() + "' > " + dir + "test0.in" + "\n");
                } else if (StrUtil.isEmpty(t.getSampleInput()) && t.getTestInputs().size() > 0) {
                    execStr.append("echo '" + t.getTestInputs().get(0) + "' > " + dir + "test0.in" + "\n");
                    execStr.append("echo '" + t.getTestInputs().get(0) + "' > " + dir + "sample.in" + "\n");
                } else {
                    execStr.append("echo '' > " + dir + "sample.in" + "\n");
                    execStr.append("echo '' > " + dir + "test0.in" + "\n");
                }

                if (StrUtil.isNotEmpty(t.getSampleOutput()) && t.getTestOutputs().size() > 0) {
                    execStr.append("echo '" + t.getTestOutputs().get(0) + "' > " + dir + "test0.out" + "\n");
                    execStr.append("echo '" + t.getSampleOutput() + "' > " + dir + "sample.out" + "\n");
                } else if (StrUtil.isNotEmpty(t.getSampleOutput()) && t.getTestOutputs().size() < 1) {
                    execStr.append("echo '" + t.getSampleOutput() + "' > " + dir + "sample.out" + "\n");
                    execStr.append("echo '" + t.getSampleOutput() + "' > " + dir + "test0.out" + "\n");
                } else if (StrUtil.isEmpty(t.getSampleOutput()) && t.getTestOutputs().size() > 0) {
                    execStr.append("echo '" + t.getTestOutputs().get(0) + "' > " + dir + "test0.out" + "\n");
                    execStr.append("echo '" + t.getTestOutputs().get(0) + "' > " + dir + "sample.out" + "\n");
                } else {
                    execStr.append("echo '' > " + dir + "sample.out" + "\n");
                    execStr.append("echo '' > " + dir + "test0.out" + "\n");
                    flag = false;
                }

                if (t.getTestOutputs().size() > 1 && t.getTestInputs().size() == t.getTestOutputs().size()) {
                    for (int i = 0; i < t.getTestOutputs().size(); i++) {
                        if (i == 0) {
                            continue;
                        }
                        execStr.append("touch " + dir + "test" + i + ".in" + "\n" + "touch " + dir + "test" + i + ".out" + "\n");
                        execStr.append("echo '" + t.getTestInputs().get(i) + "' > " + dir + "test" + i + ".in" + "\n");
                        execStr.append("echo '" + t.getTestOutputs().get(i) + "' > " + dir + "test" + i + ".out" + "\n");
                    }
                }
                String result = execCmd(execStr.toString());
                uploadProblemVO.setFlag(!"error".equals(result) && flag);
                return uploadProblemVO;
            } catch (Exception e) {
                problemMapper.deleteById(t.getProblemId());
                execCmd("rm -rf " + dir);
                uploadProblemVO.setFlag(false);
                return uploadProblemVO;
            }
        }).collect(Collectors.toList());
    }

    @Override
    public List<SimpleContestProblemDTO> listSimpleContestProblemDTO(Integer contestId) {
        return problemMapper.listSimpleContestProblemDTO(contestId);
    }

    @Override
    public Page<SimpleProblemVO> pageSimpleProblem(Integer current, Integer size, Integer sort) {
        Map<Integer, String> sortMap = Map.of(1, "problem_id", 2, "problem_id", 3, "in_date", 4, "in_date");
        QueryWrapper<SimpleProblemVO> queryWrapper = new QueryWrapper<>();
        queryWrapper.orderBy(sort != 0, (sort & 1) == 1, sortMap.get(sort));
        Page<SimpleProblemVO> page = new Page<>(current, size);
        return problemMapper.pageSimpleProblem(page, queryWrapper);
    }

    @Override
    public void downloadProblem(List<Integer> problemIds) {
        List<Problem> problems = super.listByIds(problemIds);
        List<ProblemDTO> collect = problems.stream().map(t -> {
            ProblemDTO problemDTO = new ProblemDTO();
            BeanUtils.copyProperties(t, problemDTO);
            String cmd = "ls /home/judge/data/" + t.getProblemId() + "/test*.in";
            String res = execCmd(cmd);
            Assert.isTrue(!"error".equals(res), "查询题目失败");
            List<String> collect1 = IntStream.range(0, res.split("\n").length - 1).mapToObj(i -> {
                String queryCmd = "cat /home/judge/data/" + t.getProblemId() + "/test" + i + ".in";
                return execCmd(queryCmd);
            }).collect(Collectors.toList());
            problemDTO.setTestInputs(collect1);
            String cmd2 = "ls /home/judge/data/" + t.getProblemId() + "/test*.out";
            String res2 = execCmd(cmd2);
            Assert.isTrue(!"error".equals(res2), "查询题目失败");
            List<String> collect2 = IntStream.range(0, res2.split("\n").length).mapToObj(i -> {
                String queryCmd = "cat /home/judge/data/" + t.getProblemId() + "/test" + i + ".out";
                return execCmd(queryCmd);
            }).collect(Collectors.toList());
            problemDTO.setTestOutputs(collect2);
            return problemDTO;
        }).collect(Collectors.toList());
        ProblemList problemList = new ProblemList();
        problemList.setProblemList(collect);
        problemList.setGenerator("goj");
        boolean flag = true;
        try {
            HttpServletResponse response = RequestUtil.response();
            response.setContentType("multipart/form-data;charset=UTF-8");
            response.setCharacterEncoding("UTF-8");
            response.setHeader("Content-Disposition", "attachment;fileName=" + java.net.URLEncoder.encode("Problems.xml", StandardCharsets.UTF_8));
            ServletOutputStream out = response.getOutputStream();
            out.write(XStreamUtil.toXml(problemList).getBytes());
            out.flush();
            out.close();
        } catch (IOException e) {
            flag = false;
            e.printStackTrace();
        }
        Assert.isTrue(flag, "下载失败");
    }

}
